import org.junit.Test;
import static org.junit.Assert.*;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

public class ScopeTests {

	private FileInputStream openFile(String filename) {
		try {
			FileInputStream fileContents = new FileInputStream(filename);
			return fileContents;
		}
		catch (FileNotFoundException e) {
			fail("Ficheiro " + filename + " nao existente");
			return null;
		}
	}

	private Scope getScopeOfFile(String filename) {
		eg1 parser = new eg1(openFile(filename));

		SimpleNode runOutput = null;
		try {
			runOutput = parser.Start();
			runOutput.dump("");
		}
		catch (ParseException e) {
			fail();
		}

		return new Scope(null, runOutput);
	}

	// Isto podia ser um Function.compareTo, mas parece-me melhor
	// estar definido aqui
	private void compareFunction(Function function, String name, String returnName, String returnType, Map<String, String> arguments) {
		assertEquals(function.getFunctionName(), name);

		if (returnName == null) {
			assertNull(function.getReturnName());
			assertNull(function.getReturnType());
		}
		else {
			assertEquals(function.getReturnName(), returnName);
			assertEquals(function.getReturnType(), returnType);
		}

		if (arguments == null)
			assertNull(function.getArguments());
		else {
			Map<String, String> functionArguments = function.getArguments();

			for (Map.Entry<String, String> argument : arguments.entrySet()) {
				String argumentName = argument.getKey();
				String argumentType = argument.getValue();

				assertTrue(functionArguments.containsKey(argumentName));
				assertEquals(functionArguments.get(argumentName), argumentType);
			}
		}
	}

	
    @Test
	public void testArray1FileScope() {
		String filename = "MyFirstYalExamples/array1.yal";

		Scope table = getScopeOfFile(filename);

		/* Este ficheiro tem 2 funcoes:
		 * function print_array(N)
		 * function main()
		 */
		Function printArray = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		Map<String, String> printArrayArguments = new HashMap<String, String>();
		printArrayArguments.put("N", "int");

		compareFunction(printArray, "print_array", null, null, printArrayArguments);

		compareFunction(main, "main", null, null, null);
	}

	@Test
	public void testArray2FileScope() {
		String filename = "MyFirstYalExamples/array2.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function sum = sum_array(A[])
		 * function main()
		 */

		Function sumArray = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		Map<String, String> sumArrayArguments = new HashMap<String, String>();
		sumArrayArguments.put("A", "int[]");

		compareFunction(sumArray, "sum_array", "sum", "int", sumArrayArguments);

		compareFunction(main, "main", null, null, null);
	}

	@Test
	public void testAval1FileScope() {
		String filename = "MyFirstYalExamples/aval1.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function main()
		 * function a=f(b, c)
		 */
		Function main = table.getFunctionsInScope().get(0);
		Function f = table.getFunctionsInScope().get(1);

		compareFunction(main, "main", null, null, null);

		Map<String, String> fArguments = new HashMap<String, String>();
		fArguments.put("b", "int");
		fArguments.put("c", "int");

		compareFunction(f, "f", "a", "int", fArguments);
	}

	@Test
	public void testAval2FileScope() {
		String filename = "MyFirstYalExamples/aval2.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function c=f(a, b)
		 * function main()
		 */
		Function f = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);
		
		Map<String, String> fArguments = new HashMap<String, String>();
		fArguments.put("a", "int");
		fArguments.put("b", "int");
		compareFunction(f, "f", "c", "int", fArguments);
		
		compareFunction(main, "main", null, null, null);
	}

	@Test
	public void testAval3FileScope() {
		String filename = "MyFirstYalExamples/aval3.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function c=f(a, b)
		 * function main()
		 */
		Function f = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);
		
		Map<String, String> fArguments = new HashMap<String, String>();
		fArguments.put("a", "int");
		fArguments.put("b", "int");
		compareFunction(f, "f", "c", "int", fArguments);
		
		compareFunction(main, "main", null, null, null);
	}

	@Test
	public void testAval4FileScope() {
		String filename = "MyFirstYalExamples/aval4.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function c = f(a, b)
		 * function main()
		 */
		Function f = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);
		
		Map<String, String> fArguments = new HashMap<String, String>();
		fArguments.put("a", "int");
		fArguments.put("b", "int");
		compareFunction(f, "f", "c", "int", fArguments);
		
		compareFunction(main, "main", null, null, null);
	}

	@Test
	public void testAval5FileScope() {
		String filename = "MyFirstYalExamples/aval5.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function c=f(a, c)
		 * function main()
		 */
		Function f = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);
		
		Map<String, String> fArguments = new HashMap<String, String>();
		fArguments.put("a", "int");
		fArguments.put("c", "int");
		compareFunction(f, "f", "c", "int", fArguments);
		
		compareFunction(main, "main", null, null, null);
	}

	@Test
	public void testAval6FileScope() {
		String filename = "MyFirstYalExamples/aval6.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function a=sqrt(vsqn)
		 * function main()
		 */
		Function sqrt = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		Map<String, String> sqrtArguments = new HashMap<String, String>();
		sqrtArguments.put("vsqn", "int");
		compareFunction(sqrt, "sqrt", "a", "int", sqrtArguments);
		
		compareFunction(main, "main", null, null, null);		
	}

	@Test
	public void testAval7FileScope() {
		String filename = "MyFirstYalExamples/aval7.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function cont=Count(word)
		 * function main()
		 */
		Function count = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		Map<String, String> countArguments = new HashMap<String, String>();
		countArguments.put("word", "int");

		compareFunction(count, "Count", "cont", "int", countArguments);
		
		compareFunction(main, "main", null, null, null);		
	}

	@Test
	public void testAval8FileScope() {
		String filename = "MyFirstYalExamples/aval8.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function max=max1()
		 * function main()
		 */
		Function max1 = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		compareFunction(max1, "max1", "max", "int", null);
		compareFunction(main, "main", null, null, null);		
	}

	@Test
	public void testLibrary1FileScope() {
		String filename = "MyFirstYalExamples/library1.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function m=max(a,b)
		 * function m=min(a,b)
		 */
		Function max = table.getFunctionsInScope().get(0);
		Function min = table.getFunctionsInScope().get(1);

		Map<String, String> maxArguments = new HashMap<String, String>();
		maxArguments.put("a", "int");
		maxArguments.put("b", "int");

		compareFunction(max, "max", "m", "int", maxArguments);
		
		Map<String, String> minArguments = new HashMap<String, String>();
		minArguments.put("a", "int");
		minArguments.put("b", "int");

		compareFunction(min, "min", "m", "int", minArguments);
	}

	@Test
	public void testMax1FileScope() {
		String filename = "MyFirstYalExamples/max1.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function max=max()
		 * function main()
		 */
		Function max = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		compareFunction(max, "max", "max", "int", null);
		compareFunction(main, "main", null, null, null);		
	}

	@Test
	public void testMax_arrayFileScope() {
		String filename = "MyFirstYalExamples/max_array.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function max=maxarray(a[])
		 * function main()
		 */
		Function maxArray = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		Map<String, String> maxArrayArguments = new HashMap<String, String>();
		maxArrayArguments.put("a", "int[]");
		compareFunction(maxArray, "maxarray", "max", "int", maxArrayArguments);
		
		compareFunction(main, "main", null, null, null);		
	}

	@Test
	public void testMaxminFileScope() {
		String filename = "MyFirstYalExamples/maxmin.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function a=maxmin()
		 * function main()
		 */
		Function maxmin = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		compareFunction(maxmin, "maxmin", "a", "int", null);
		compareFunction(main, "main", null, null, null);		
	}

	@Test
	public void testMaxFileScope() {
		String filename = "MyFirstYalExamples/max.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function max=max(a, b, c, d, e)
		 * function main()
		 */
		Function max = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		Map<String, String> maxArguments = new HashMap<String, String>();

		maxArguments.put("a", "int");
		maxArguments.put("b", "int");
		maxArguments.put("c", "int");
		maxArguments.put("d", "int");
		maxArguments.put("e", "int");

		compareFunction(max, "max", "max", "int", maxArguments);
		
		compareFunction(main, "main", null, null, null);		
	}

	@Test
	public void testPrograma1FileScope() {
		String filename = "MyFirstYalExamples/programa1.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function det(d[])
		 * function main()
		 */
		Function det = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		Map<String, String> detArguments = new HashMap<String, String>();
		detArguments.put("d", "int[]");

		compareFunction(det, "det", null, null, detArguments);
		
		compareFunction(main, "main", null, null, null);		
	}

	@Test
	public void testPrograma2FileScope() {
		String filename = "MyFirstYalExamples/programa2.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 3 funcoes:
		 * function a[]=f1(b[])
		 * function a[]=f2(N)
		 * function main()
		 */
		Function f1 = table.getFunctionsInScope().get(0);
		Function f2 = table.getFunctionsInScope().get(1);
		Function main = table.getFunctionsInScope().get(2);

		Map<String, String> f1Arguments = new HashMap<String, String>();
		f1Arguments.put("b", "int[]");
		compareFunction(f1, "f1", "a", "int[]", f1Arguments);

		Map<String, String> f2Arguments = new HashMap<String, String>();
		f2Arguments.put("N", "int");
		compareFunction(f2, "f2", "a", "int[]", f2Arguments);

		compareFunction(main, "main", null, null, null);		
	}

	@Test
	public void testPrograma3FileScope() {
		String filename = "MyFirstYalExamples/programa3.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 3 funcoes:
		 * function a[]=f1(b[])
		 * function a[]=f2(N)
		 * function main()
		 */
		Function f1 = table.getFunctionsInScope().get(0);
		Function f2 = table.getFunctionsInScope().get(1);
		Function main = table.getFunctionsInScope().get(2);

		Map<String, String> f1Arguments = new HashMap<String, String>();
		f1Arguments.put("b", "int[]");
		compareFunction(f1, "f1", "a", "int[]", f1Arguments);

		Map<String, String> f2Arguments = new HashMap<String, String>();
		f2Arguments.put("N", "int");
		compareFunction(f2, "f2", "a", "int[]", f2Arguments);

		compareFunction(main, "main", null, null, null);		
	}

	@Test
	public void testSqrtFileScope() {
		String filename = "MyFirstYalExamples/sqrt.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 1 funcoes:
		 * function a=sqrt()
		 * function main()
		 */
		Function sqrt = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		compareFunction(sqrt, "sqrt", "a", "int", null);
		compareFunction(main, "main", null, null, null);		
	}
	
	private void checkIfVariablesExistInScope(Scope scope, Map<String, String> variables) {
		for (Map.Entry<String, String> variable : variables.entrySet()) {
			String variableName = variable.getKey();
			String variableType = variable.getValue();
			
			assertNotNull(scope.lookup(variableName));
			assertEquals(scope.lookup(variableName).getVariableType(), variableType);
		}
	}

	@Test
	public void testVariablesInScopeArray1() {
		String filename = "MyFirstYalExamples/array1.yal";

		Scope table = getScopeOfFile(filename);

		/* Este ficheiro tem 2 funcoes:
		 * function print_array(N)
		 * function main()
		 */
		Function printArray = table.getFunctionsInScope().get(0);

		/* Existem 3 variaveis presentes no scope topo da funcao print_array(N)
		 * N
		 * a=[N]
   		 * i=0
		 */
		Map<String, String> printArrayVariables = new HashMap<String, String>();
		printArrayVariables.put("N", "int");
		printArrayVariables.put("a", "int[]");
		printArrayVariables.put("i", "int");
		
		checkIfVariablesExistInScope(printArray.getFunctionScope(), printArrayVariables);
		
		// Analisar o scope do primeiro while
		List<String> scopeList = new LinkedList<String>();
		scopeList.add("while1");
		
		Map<String, String> while1Variables = new HashMap<String, String>();
		
		// Variaveis disponiveis dentro do scope do primeiro while
		while1Variables.put("i", "int");
		while1Variables.put("a", "int[]");
		while1Variables.put("N", "int");
		
		checkIfVariablesExistInScope(printArray.getInsideScope(scopeList), while1Variables);
		
		// Analisar o scope do segundo while
		scopeList = new LinkedList<String>();
		scopeList.add("while2");
		
		Map<String, String> while2Variables = new HashMap<String, String>();
		
		// Variaveis disponiveis dentro do scope do segundo while
		while2Variables.put("ai", "int");
		while2Variables.put("i", "int");
		while2Variables.put("a", "int[]");
		while2Variables.put("N", "int");
		
		checkIfVariablesExistInScope(printArray.getInsideScope(scopeList), while2Variables);
	}
	
	@Test
	public void testVariablesInScopeArray2() {
		String filename = "MyFirstYalExamples/array2.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function sum = sum_array(A[])
		 * function main()
		 */

		Function sumArray = table.getFunctionsInScope().get(0);
		Function main = table.getFunctionsInScope().get(1);

		/* Existem 2 variaveis presentes no scope topo da funcao sum_array(A[])
		 * A[]
		 * i=0
		 */
		Map<String, String> sumArrayVariables = new HashMap<String, String>();
		sumArrayVariables.put("A", "int[]");
		sumArrayVariables.put("i", "int");
		checkIfVariablesExistInScope(sumArray.getFunctionScope(), sumArrayVariables);
		
		// Analisar o scope do while da funcao sum_array
		List<String> scopeList = new LinkedList<String>();
		scopeList.add("while1");
		
		Map<String, String> while1Variables = new HashMap<String, String>();
		
		// Variaveis disponiveis dentro do scope do while da funcao sum_array
		while1Variables.put("sum", "int");
		while1Variables.put("i", "int");
		while1Variables.put("A", "int[]");
		
		checkIfVariablesExistInScope(sumArray.getInsideScope(scopeList), while1Variables);
		
		// Analisar o scope do while da funcao main
		while1Variables = new HashMap<String, String>();
		
		// Variaveis disponiveis dentro do scope do while da funcao main
		while1Variables.put("N", "int");
		while1Variables.put("a", "int[]");
		while1Variables.put("i", "int");
		
		// TODO: Confirmar que deve ser possivel obter o valor desta variavel
		//while1Variables.put("sum", "int");

		checkIfVariablesExistInScope(main.getInsideScope(scopeList), while1Variables);
	}
	
	@Test
	public void testVariablesInScopeAval1() {
		String filename = "MyFirstYalExamples/aval1.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function main()
		 * function a=f(b, c)
		 */
		Function f = table.getFunctionsInScope().get(1);

		/* Existem 3 variaveis presentes no scope topo da funcao a=f(b, c)
		 * a
		 * b
		 * c
		 */
		Map<String, String> fVariables = new HashMap<String, String>();
		fVariables.put("a", "int");
		fVariables.put("b", "int");
		fVariables.put("c", "int");
		checkIfVariablesExistInScope(f.getFunctionScope(), fVariables);

		// TODO: Fazer o main?
	}
	
	@Test
	public void testVariablesInScopeAval2() {
		String filename = "MyFirstYalExamples/aval2.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function c=f(a, b)
		 * function main()
		 */
		Function f = table.getFunctionsInScope().get(0);

		/* Existem 2 variaveis presentes no scope topo da funcao c=f(a,b)
		 * a
		 * b
		 */
		Map<String, String> fVariables = new HashMap<String, String>();
		fVariables.put("a", "int");
		fVariables.put("b", "int");
		checkIfVariablesExistInScope(f.getFunctionScope(), fVariables);
		
		// Analisar o scope do if dentro da funcao f
		List<String> scopeList = new LinkedList<String>();
		scopeList.add("if1");
		
		Map<String, String> if1Variables = new HashMap<String, String>();
		
		if1Variables.put("a", "int");
		if1Variables.put("b", "int");
		if1Variables.put("c", "int");
		
		checkIfVariablesExistInScope(f.getInsideScope(scopeList), if1Variables);
	}
	
	@Test
	public void testVariablesInScopeAval3() {
		String filename = "MyFirstYalExamples/aval3.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function c=f(a, b)
		 * function main()
		 */
		Function f = table.getFunctionsInScope().get(0);
		
		// Analisar o scope do if dentro da funcao f
		List<String> scopeList = new LinkedList<String>();
		scopeList.add("if1");
		
		Map<String, String> if1Variables = new HashMap<String, String>();
		
		if1Variables.put("a", "int");
		if1Variables.put("b", "int");
		if1Variables.put("c", "int");
		
		checkIfVariablesExistInScope(f.getInsideScope(scopeList), if1Variables);
		
		// Analisar o scope do else dentro da funcao f
		scopeList = new LinkedList<String>();
		scopeList.add("else1");
		
		Map<String, String> else1Variables = new HashMap<String, String>();
		
		else1Variables.put("a", "int");
		else1Variables.put("b", "int");
		else1Variables.put("c", "int");
		
		checkIfVariablesExistInScope(f.getInsideScope(scopeList), else1Variables);
	}
	
	@Test
	public void testVariablesInScopeAval5() {
		String filename = "MyFirstYalExamples/aval5.yal";
		
		Scope table = getScopeOfFile(filename);
		
		/* Este ficheiro tem 2 funcoes:
		 * function c=f(a, c)
		 * function main()
		 */
		Function f = table.getFunctionsInScope().get(0);
		
		/* TODO: Completar isto
		// Analisar o scope do if dentro da funcao f
		List<String>
		*/
	}
}
